package oop.ex3.nameserver;

import java.io.File;
import java.net.InetSocketAddress;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.Iterator;
import java.util.LinkedHashSet;
import java.util.Set;
import java.util.TreeSet;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentLinkedQueue;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.concurrent.locks.*;
import java.util.concurrent.locks.ReentrantReadWriteLock.ReadLock;
import java.util.concurrent.locks.ReentrantReadWriteLock.WriteLock;

public class NSContext {
	
	ExecutorService _executor;
	
	//NS data
	private ReentrantReadWriteLock _nsLock;
	private ReadLock _nsRlock;
	private WriteLock _nsWlock;
	private LinkedHashSet<InetSocketAddress> _nsList;
	private ConcurrentLinkedQueue<InetSocketAddress> _copyOfNS;
	private volatile AtomicBoolean _changedNs;

	//Files and FMs data
	ConcurrentHashMap<String, Set<InetSocketAddress>> _files;
	private ReentrantReadWriteLock _fmPerFileLock;
	private ReadLock _fmPerFileRlock;
	private WriteLock _fmPerFileWlock;
	ConcurrentHashMap<InetSocketAddress, Set<String>> _fms;
	
	public NSContext(ExecutorService executor){
		
		_executor = executor;
		
		//TODO this is how its done?
		_nsLock = new ReentrantReadWriteLock();
		_nsRlock = _nsLock.readLock();
		_nsWlock = _nsLock.writeLock();
		_nsList = new LinkedHashSet<InetSocketAddress>();
		_copyOfNS = new ConcurrentLinkedQueue<InetSocketAddress>();
		_changedNs = new AtomicBoolean();
		
		//creating Files and FMs array
		_files = new ConcurrentHashMap<String, Set<InetSocketAddress>>();
		_fmPerFileLock = new ReentrantReadWriteLock();
		_fmPerFileRlock = _nsLock.readLock();
		_fmPerFileWlock = _nsLock.writeLock();
		_fms = new ConcurrentHashMap<InetSocketAddress, Set<String>>();
	}
	
	public void shutDownExecutor(){
		_executor.shutdown();
	}
	
	public boolean fmContains(InetSocketAddress s){
		//TODO delete
		System.out.println("print fms");
		for (InetSocketAddress fm : _fms.keySet()){
			System.out.println(fm.getHostName()+":"+fm.getPort());	
		}
		//TODO
		return _fms.contains(s);
//		_nsRlock.lock();
//		try {
//			return _nsList.contains(s);
//		} finally {
//			_nsRlock.unlock();
//		}
	}
	
	public boolean containsFile(String file){
		return _files.contains(file); 
	}
	
	public Set<InetSocketAddress> getFmWithFile(String file){
		return _files.get(file);
	}
	
	public void addFile(String fileName, InetSocketAddress fm){
		
		//add to files collection
		_files.putIfAbsent(fileName, 
				Collections.newSetFromMap(new 
						ConcurrentHashMap<InetSocketAddress,Boolean>()));
		_files.get(fileName).add(fm);
		
		//add to FMs collection
		//TODO maybe this doesnt have to be tree set? and maby i dont really need this
		_fms.putIfAbsent(fm, new TreeSet<String>());
		_fms.get(fm).add(fileName);
		
//		//TODO delete 
//		System.out.println("printing fms");
//		for (InetSocketAddress fm1 : _fms.keySet()){
//			System.out.println(fm1.getPort());
//		}
	}
	
	//TODO check late if you need it to be synchronized
	public void deleteFromFileList(String fileName, 
			InetSocketAddress fm) {
		//remove from files array
		if (_files.contains(fileName)){
			_files.get(fileName).remove(fm);
			if (_files.get(fileName).isEmpty()){
				//TODO what it someone addSomething to the Set now??
				_files.remove(fileName);
			}
		}
	}
	
	public void deleteFromFm(String fileName, InetSocketAddress fm){
		//remove from Fm's array
		_fms.get(fm).remove(fileName);
	}
	
	public  Set<String> getFiles(){
		return _files.keySet();
	}
	
	public Set<String> removeFm(InetSocketAddress fm){
		Set<String> files = _fms.get(fm);
		_fms.remove(fm);
		return files;
	}
	
	
	public void addNS(InetSocketAddress ns){
		
		_nsWlock.lock();
		try {
			_nsList.add(ns);	
		} finally {
			_nsWlock.unlock();
			_changedNs.set(true);	
		}
	}
	
	public void addNS(ArrayList<InetSocketAddress> ns){
		_nsWlock.lock();
		try {
			_nsList.addAll(ns);	
		} finally {
			_nsWlock.unlock();
			_changedNs.set(true);		
		}
	
	}
	
	public Iterator viewNS(){
		if (_changedNs.get()){
			_nsRlock.lock();
			try {
				//TODO not very efficient, think how you change this
				_copyOfNS = 
					new ConcurrentLinkedQueue<InetSocketAddress>(_nsList);	
			} finally {
				_nsRlock.unlock();	
			}
		}
		return _copyOfNS.iterator();		
	}

	//TODO dont need it
	public boolean fmIsEmpty() {
		return _fms.isEmpty();
	}	
	
}

